/*
 * msg_server.c
 *
 *  Created on: 22/11/2012
 *      Author: julian
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include "posix_mq_utils.h"
#include "msg_server.h"
#include "socket_interface.h"

int _s;
struct sockaddr_in _myName;
socklen_t client_name_size;
struct sockaddr_in _clientName;
node_t node_ids;

static pthread_mutex_t nodeids_mutex;

int nodeListEmpty() {
    return (node_ids.list.next == 0 && node_ids.list.prev == 0);
}

void node_list_init() {

    pthread_mutex_lock(&nodeids_mutex);

    wpan_node_t *nodes = (wpan_node_t*) getNodeList();
    struct list_head *pos, *q;
    wpan_node_t *tmp;
    node_t *j;

    if (node_ids.list.next != 0 && node_ids.list.prev != 0) {
#ifdef MSG_SERVER_DEBUG
        printf("\ndeleting list.");
#endif
        list_for_each_safe(pos, q, &node_ids.list) {
            j = list_entry(pos, node_t, list);
            list_del(pos);
            free(j);
        }
    }

    INIT_LIST_HEAD(&node_ids.list);

    list_for_each_safe(pos, q, &nodes->node_list) {

        tmp = list_entry(pos, wpan_node_t, node_list);
        j = (node_t*) malloc(sizeof(node_t));

        strcpy(j->id, tmp->identifier);
        list_add(&(j->list), &(node_ids.list));
    }

    pthread_mutex_unlock(&nodeids_mutex);

}

int sendNodeList(int s) {

    struct list_head *pos;
    node_t *j;
    int r = -2;
    int size = getNumNodes();

    printf("\n size--> %d \n", size);
    if (size <= 0 || nodeListEmpty()) {
        fprintf(stderr, "\nNo nodes reported.\n");
        return -1;
    }

    if ((r = socketSend(s, &size, sizeof(int), NULL, 0, 0,
            SOCKET_SEND_TIMEOUT_MSEC)) < 0) {
#ifdef MSG_SERVER_DEBUG
    perror("sendNodeList.socketSend.Error enviando tamaño de la lista de nodos");
#endif
        if (r == -2) {
            return (-1);
        }
    }

    list_for_each(pos, &node_ids.list) {
        j = list_entry(pos, node_t, list);

        r = socketSend(s, j->id, 20, NULL, 0, 0, SOCKET_SEND_TIMEOUT_MSEC);
        if (r < 0) {
#ifdef MSG_SERVER_DEBUG
            perror("sendNodeList.socketSend.Error enviando identificador");
#endif
            if (r == -2) {
                return (-1);
            }
        }

        printf(" sent--> %d ", r);
        printf("id--> %s\n", j->id);

    }
    return (0);

}

int msgServerInit() {

    if (appLayerInit() == -1) {
#ifdef MSG_SERVER_DEBUG
        perror("msgServerinit.appLayerInit.Error al inicializar");
#endif
        return (-1);
    }

    pthread_mutex_init(&nodeids_mutex, NULL);

    node_list_init();

    messageInit();

    if ((_s = openSocket(&_myName)) < 0) {
#ifdef MSG_SERVER_DEBUG
        perror("msgServerInit.openSocket.Error al abrir socket");
#endif
        return -1;
    }

    return (0);

}

int nodeListUpdate() {

    uint8_t ret = 0;
    uint8_t i = 0;
    do {
        ret = nodeDiscovery();
        if (ret == -1) {
            return (-1);
        }
        i++;
    } while (ret < 1 && i < 5);

    if (ret <= 0) {
        setNumNodes(0);
        return -1;
    }

    setNumNodes(ret);

    printNodes();

    node_list_init();

    return (0);
}

int msgServer() {

    int s = -1;

    while (1) {

        if ((s = acceptClient(_s)) > 0) {
            attendClient(s);

        } else {
#ifdef MSG_SERVER_DEBUG
            perror("msgServer.accept");
#endif
        }
    }
    return (0);

}

int deliverResponse(int s, uint8_t command, char *id) {

    int r = -1;
    struct msg_buf cbuf;

    if (id == NULL) {
        errno = EINVAL;
        return (-1);
    }

    wpan_node_t *node = findNodeById(id);
    if (node == NULL) {
        return (-1);
    }

    uint16_t prio = 2;

    if (pmsgq_receive(node->msqid, &cbuf, sizeof(cbuf), (unsigned int*) &prio,
            COMMAND_RESPONSE_TIMEOUT_MSEC, COMMAND_RESPONSE_TIMEOUT_SEC) == 0) {
//    if (pmsgq_receive(node->msqid, &cbuf.msg, sizeof(struct msg_t), (unsigned int*) &prio,
//            COMMAND_RESPONSE_TIMEOUT_MSEC, COMMAND_RESPONSE_TIMEOUT_SEC) == 0) {

#ifdef MSG_SERVER_DEBUG
        print_msg_t(cbuf.msg);
        printf("\nprio: %d\n", prio);
#endif

        r = socketSend(s, &cbuf.msg, sizeof(struct msg_t), NULL, 0, 0,
                SOCKET_SEND_TIMEOUT_MSEC);
        if (r < 0) {
#ifdef MSG_SERVER_DEBUG
            perror("deliverResponse:socketSend.Error al entregar respuesta");
#endif
            return (-1);
        }

        return (0);
    } else {
#ifdef MSG_SERVER_DEBUG
        perror("deliverResponse:pmsgq_recv.Error al retirar respuesta de la cola de mensajes");
#endif
        return -1;
    }

}

int msgServerExit() {

    if (appLayerExit() == -1) {
#ifdef MSG_SERVER_DEBUG
        perror("msgServerExit.appExit.Error al finalizar");
#endif
        return (-1);
    }

    struct list_head *pos, *q;
    node_t *j;

    if (!list_empty(&node_ids.list)) {
        pthread_mutex_lock(&nodeids_mutex);
        list_for_each_safe(pos, q, &node_ids.list) {
            j = list_entry(pos, node_t, list);
            list_del(pos);
            free(j);
        }
        pthread_mutex_unlock(&nodeids_mutex);
    }

    pthread_mutex_destroy(&nodeids_mutex);
    close(_s);
    return (0);
}

int acceptClient(int fd) {

    int s = -1;

    s = accept(fd, (struct sockaddr*) &_clientName, &client_name_size);

    if (s < 0) {
#ifdef MSG_SERVER_DEBUG
        perror("acceptClient.error al aceptar conexión");
#endif
        return -1;
    }

    return s;
}

void* clientThread(void* arg) {

    int s = *(int*) arg;
    struct msg_t msg;
    int r, ret;
    uint8_t t = 0;

    struct timeval timeout;
    fd_set set;
    uint8_t current = 0;
    abpSpecific(&current);

    uint8_t ufid = 2;
    msgFidSpecific(&ufid);

    timeout.tv_sec = 5;
    timeout.tv_usec = 0;
    FD_ZERO(&set);
    FD_SET(s, &set);

    while ((r = recv(s, &msg, sizeof(struct msg_t), 0)) > 0) {

        if (r > 0) {
#ifdef MSG_SERVER_DEBUG
            printf("\nSocket: %d .Request size %d\n", s, r);
            print_msg_t(msg);
#endif
            switch (msg.type) {
//            case SERVER_EXIT:
//                close(s);
//                break;
            case NODE_LIST:
                sendNodeList(s);
                break;
            case NODE_LIST_UPDATE:
                if (nodeListUpdate() == -1) {
                    break;
                } else {
                    sendNodeList(s);
                }
                break;
            default: {
                if (msg.options & MULTICAST_BIT) {
                    dest_list_t a;
                    a.size = (uint8_t) msg.nodeId[0];
                    char *c[a.size];
                    char buf[20];

                    for (t = 0; t < a.size; t++) {

                        r = socketReceive(s, buf, sizeof(buf), NULL, 0, 0,
                                SOCKET_RCV_TIMEOUT_MSEC);
                        if (r < 0) {
                            if (r == -2) {
#ifdef MSG_SERVER_DEBUG
                                perror("clientThr.SocketReceive.Error al recibir id");
#endif
                            }
                        } else {
                            c[t] = malloc(r);
                            strncpy(c[t], buf, r);
                        }
                    }
                    if (t < a.size) {
                        break;
                    }
                    a.id_list = c;
#ifdef MSG_SERVER_DEBUG
                    for (t = 0; t < a.size; t++) {
                        printf("\nid%d--%s", t, a.id_list[t]);
                    }
#endif

                    ret = msgMulticast(&msg, &a);

                    for (t = 0; t < a.size; t++) {
                        free(c[t]);
                    }

                } else {
#ifdef MSG_SERVER_DEBUG
                    printf("\nuni--%s", msg.nodeId);
#endif
                    ret = msgOutput(&msg);
                }

                //--------------------------------------------------send response-------------

                r = socketSend(s, &ret, sizeof(int), NULL, 0, 0,
                        SOCKET_SEND_TIMEOUT_MSEC);
                if (r == -2) {
#ifdef MSG_SERVER_DEBUG
                    perror("clientThr.socketSend.Error al enviar valor de retorno");
#endif
                }

                if ((msg.options & RESPONSE_REQUIRED_BIT) && (ret >= 0)) {

                    if (deliverResponse(s, msg.type, msg.nodeId) == -1) {
#ifdef MSG_SERVER_DEBUG
                       fprintf(stderr,"clientThr.deliverResponse.Error al entregar respuesta");
#endif
                    }
                }
                break;
            }
            }
            timeout.tv_sec = 5;
            timeout.tv_usec = 0;
        } else {
            //if (r == -2) {
#ifdef MSG_SERVER_DEBUG
            perror("clientThr.recv");
            fprintf(stderr, " on socket %d\n", s);
#endif
            //}
            printf("\n...");
            break;
        }
    }
    close(s);
    printf("\n...client %d exits\n", s);
    pthread_exit(NULL);

}

int attendClient(int fd) {

    int s = fd;

    pthread_t thr;

    pthread_attr_t attr;

    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

    if (pthread_create(&thr, &attr, clientThread, &s) < 0) {
#ifdef MSG_SERVER_DEBUG
     perror("attendClient:pthread_create.Error creando thread para atención");
#endif
        return -1;
    }
    pthread_attr_destroy(&attr);

    return 0;
}

